// Copyright (C) 2012 Yury Bychenkov (http://bychenkov.com).
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//  - Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//  - Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//  - Neither the name of Yury Bychenkov nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "mappedfile.h"

Logger MappedFile::logger("MappedFile");

MappedFile::MappedFile():
#ifdef WINDOWS64
	hFile(0),
	hFileMapping(0),
#endif
#ifdef POSIX64
	fd(0),
#endif
	ptr(0),
	fileSize(0)
{
}

MappedFile::~MappedFile() {
	close();
}

bool MappedFile::isOpened() const {
	return ptr != 0;
}

uint64 MappedFile::size() const {
	return fileSize;
}

uint8* MappedFile::data() const {
	return (uint8*) ptr;
}

void MappedFile::close() {
	close(0xFFFFFFFFFFFFFFFFULL);
}

MappedFile* MappedFile::open(const char* file, uint64 size, bool readOnly) {
	MappedFile* mappedFile = new MappedFile();

	mappedFile->init(file, size, readOnly);

	if (!mappedFile->isOpened()) {
		delete mappedFile;
		return 0;
	}

	return mappedFile;
}


#ifdef WINDOWS64

void MappedFile::init(const char* file, uint64 size, bool readOnly) {

    DWORD dwAccess = GENERIC_READ | (readOnly? 0: GENERIC_WRITE);
    DWORD dwCreationDisposition = readOnly? OPEN_EXISTING: OPEN_ALWAYS;
    DWORD dwShareMode = FILE_SHARE_READ | (readOnly? FILE_SHARE_WRITE: 0);

	HANDLE hFile = ::CreateFile(file, dwAccess, dwShareMode, NULL, dwCreationDisposition, 0, NULL);
	if (hFile == INVALID_HANDLE_VALUE) {
		logger.error("Can't create file: ", file);
		return;
	}

	if (size == 0) {
		DWORD sizeHigh;
		size = ::GetFileSize(hFile, &sizeHigh);
		size |= ((uint64) sizeHigh) << 32;
	}
	
	if (size == 0) {
		logger.error("Empty file: ", file);
		::CloseHandle(hFile);
		return;
	}

	HANDLE hFileMapping = ::CreateFileMapping(hFile, NULL, readOnly? PAGE_READONLY: PAGE_READWRITE, (DWORD)(size >> 32), (DWORD) size, NULL);
	if (hFileMapping == 0) {
		logger.error("Unable to create mapping: ", file);
		::CloseHandle(hFile);
		return;
	}

	LPVOID ptr = ::MapViewOfFile(hFileMapping, readOnly? FILE_MAP_READ: FILE_MAP_WRITE, 0, 0, 0);
	if (ptr == 0) {
		logger.error("Unable to create map view: ", file);
		::CloseHandle(hFileMapping);
		::CloseHandle(hFile);
		return;
	}

	this->hFile = hFile;
	this->hFileMapping = hFileMapping;
	this->ptr = ptr;
	this->fileSize = size;
}

void MappedFile::close(uint64 truncateSize) {

	if (ptr != 0) {
		::UnmapViewOfFile(ptr);
		ptr = 0;
		fileSize = 0;
	}

	if (hFileMapping != 0) {
		::CloseHandle(hFileMapping);
		hFileMapping = 0;
	} 

	if (hFile != 0) {
		if (truncateSize != 0xFFFFFFFFFFFFFFFFULL) {
			LONG lowSize = (LONG) truncateSize;
			LONG highSize = (LONG) (truncateSize >> 32);
			::SetFilePointer(hFile, lowSize, &highSize, FILE_BEGIN);
			if (::GetLastError() == NO_ERROR) {
				::SetEndOfFile(hFile);
			}
		}
		::CloseHandle(hFile);
		hFile = 0;
	} 
}

#endif // WINDOWS64



#ifdef POSIX64

void MappedFile::init(const char* file, uint64 size, bool readOnly) {

	int fd = ::open(file, readOnly ? O_RDONLY : (O_RDWR | O_CREAT), S_IRUSR | S_IWUSR);
	if (fd == -1) {
		logger.error("Can't create file: ", file);
		return;
	}

	struct stat sb;
	if (-1 == ::fstat(fd, &sb)) {
		::close(fd);
    	logger.error("Can't retrieve file status: ", file);
        return;
    }

    if (!S_ISREG(sb.st_mode)) {
		::close(fd);
    	logger.error("Not a file: ", file);
        return;    	
    }

	if (size == 0) {
		size = sb.st_size;
	}
	
	if (size == 0) {
		logger.error("Empty file: ", file);
		::close(fd);
		return;
	}

    if (!readOnly && 0 != ::ftruncate(fd, size)) {
		logger.error("Can't resize file: ", file);
		::close(fd);
		return;
    }
    
	void* ptr = ::mmap(0, size, readOnly ? PROT_READ : (PROT_READ | PROT_WRITE), MAP_SHARED, fd, 0);
	if (ptr == MAP_FAILED) {
		logger.error("Unable to create mapping: ", file);
		::close(fd);
		return;
    }

	this->fd = fd;
	this->ptr = ptr;
	this->fileSize = size;
}

void MappedFile::close(uint64 truncateSize) {

	if (ptr != 0) {
		::munmap(ptr, fileSize);
		ptr = 0;
		fileSize = 0;
	}

	if (fd != 0) {
		if (truncateSize != 0xFFFFFFFFFFFFFFFFULL) {
			if (0 != ::ftruncate(fd, truncateSize)) {
			  logger.error("Can't truncate the file");
			}
		}
		::close(fd);
		fd = 0;
	} 

}

#endif // POSIX64
